/*
 * Copyright (c) 2020 pig4cloud Authors. All Rights Reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.pig4cloud.pig.common.feign.sentinel.ext;

import com.alibaba.cloud.sentinel.feign.SentinelContractHolder;
import feign.Contract;
import feign.Feign;
import feign.InvocationHandlerFactory;
import feign.Target;
import org.springframework.beans.BeansException;
import org.springframework.cloud.openfeign.FallbackFactory;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.cloud.openfeign.FeignClientFactory;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.util.StringUtils;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.util.Map;

/**
 * 支持自动降级注入的Feign构建器，重写 {@link com.alibaba.cloud.sentinel.feign.SentinelFeign}
 * <p>
 * 扩展了Sentinel的Feign集成，支持从Spring容器中自动注入降级实现
 * </p>
 *
 * @author lengleng
 * @date 2025/05/31
 */
public final class PigSentinelFeign {

	/**
	 * 私有构造函数，防止实例化
	 */
	private PigSentinelFeign() {

	}

	/**
	 * 创建Builder实例
	 * @return Builder构建器实例
	 */
	public static PigSentinelFeign.Builder builder() {
		return new PigSentinelFeign.Builder();
	}

	/**
	 * Feign构建器实现，集成Sentinel和Spring容器
	 */
	public static final class Builder extends Feign.Builder implements ApplicationContextAware {

		/**
		 * Feign契约对象
		 */
		private Contract contract = new Contract.Default();

		/**
		 * Spring应用上下文
		 */
		private ApplicationContext applicationContext;

		/**
		 * Feign客户端工厂
		 */
		private FeignClientFactory feignClientFactory;

		/**
		 * 不支持自定义invocationHandlerFactory
		 * @param invocationHandlerFactory 调用处理器工厂
		 * @return Feign.Builder实例
		 * @throws UnsupportedOperationException 始终抛出不支持操作异常
		 */
		@Override
		public Feign.Builder invocationHandlerFactory(InvocationHandlerFactory invocationHandlerFactory) {
			throw new UnsupportedOperationException();
		}

		/**
		 * 设置契约
		 * @param contract 契约对象
		 * @return Builder实例
		 */
		@Override
		public PigSentinelFeign.Builder contract(Contract contract) {
			this.contract = contract;
			return this;
		}

		/**
		 * 构建Feign实例
		 * @return Feign实例
		 */
		@Override
		public Feign internalBuild() {
			super.invocationHandlerFactory(new InvocationHandlerFactory() {
				@Override
				public InvocationHandler create(Target target, Map<Method, MethodHandler> dispatch) {

					// 查找 FeignClient 上的 降级策略
					FeignClient feignClient = AnnotationUtils.findAnnotation(target.type(), FeignClient.class);
					Class<?> fallback = feignClient.fallback();
					Class<?> fallbackFactory = feignClient.fallbackFactory();

					String beanName = feignClient.contextId();
					if (!StringUtils.hasText(beanName)) {
						beanName = feignClient.name();
					}

					Object fallbackInstance;
					FallbackFactory<?> fallbackFactoryInstance;
					if (void.class != fallback) {
						fallbackInstance = getFromContext(beanName, "fallback", fallback, target.type());
						return new PigSentinelInvocationHandler(target, dispatch,
								new FallbackFactory.Default(fallbackInstance));
					}

					if (void.class != fallbackFactory) {
						fallbackFactoryInstance = (FallbackFactory<?>) getFromContext(beanName, "fallbackFactory",
								fallbackFactory, FallbackFactory.class);
						return new PigSentinelInvocationHandler(target, dispatch, fallbackFactoryInstance);
					}
					return new PigSentinelInvocationHandler(target, dispatch);
				}

				/**
				 * 从Spring容器获取实例
				 * @param name 客户端名称
				 * @param type 实例类型描述（fallback或fallbackFactory）
				 * @param fallbackType 降级类型
				 * @param targetType 目标类型
				 * @return 降级实例
				 * @throws IllegalStateException 当实例不存在或类型不兼容时抛出
				 */
				private Object getFromContext(String name, String type, Class<?> fallbackType, Class<?> targetType) {
					Object fallbackInstance = feignClientFactory.getInstance(name, fallbackType);
					if (fallbackInstance == null) {
						throw new IllegalStateException(String
							.format("No %s instance of type %s found for feign client %s", type, fallbackType, name));
					}

					if (!targetType.isAssignableFrom(fallbackType)) {
						throw new IllegalStateException(String.format(
								"Incompatible %s instance. Fallback/fallbackFactory of type %s is not assignable to %s for feign client %s",
								type, fallbackType, targetType, name));
					}
					return fallbackInstance;
				}
			});

			super.contract(new SentinelContractHolder(contract));
			return super.internalBuild();
		}

		/**
		 * private Object getFieldValue(Object instance, String fieldName) { Field field =
		 * ReflectionUtils.findField(instance.getClass(), fieldName);
		 * field.setAccessible(true); try { return field.get(instance); } catch
		 * (IllegalAccessException e) { // ignore } return null; }
		 **/

		/**
		 * 设置应用上下文
		 * @param applicationContext Spring应用上下文
		 * @throws BeansException Bean处理异常
		 */
		@Override
		public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
			this.applicationContext = applicationContext;
			this.feignClientFactory = this.applicationContext.getBean(FeignClientFactory.class);
		}

	}

}
